#include "image_file.h"
#include "canvas.h"
#include <ios>
#include <fstream>
#include <cassert>

using namespace krryn::view;

image_file_manager *image_file_manager::m_Instance = 0;

image_file_manager *image_file_manager::get_instance(){
	if(!m_Instance){
		m_Instance = new image_file_manager();
	}

	return m_Instance;
}

image_file_manager::image_file_manager(){
	register_image_file("tga", new image_file_tga());
	register_image_file("bmp", new image_file_bmp());
}

void image_file_manager::register_image_file(const std::string &a_Extension, image_file *a_ImageFile){
	m_ImageFiles[a_Extension] = a_ImageFile;
}

image_file *image_file_manager::find_type(const std::string &a_Filename){
	size_t l_Pos = a_Filename.rfind('.');
	const std::string l_Type = a_Filename.substr(l_Pos + 1);
	return m_ImageFiles[l_Type];
}

void image_file_manager::store(const std::string &a_Filename, canvas *a_Canvas){
	if(image_file *a_ImageFile = find_type(a_Filename)){
		std::ofstream l_File(a_Filename.c_str(), std::ios::binary | std::ios::out);
		l_File.exceptions(std::ios::failbit | std::ios::badbit);
		a_ImageFile->store(l_File, a_Canvas);
	}
}

canvas *image_file_manager::load(const std::string &a_Filename){
	if(image_file *a_ImageFile = find_type(a_Filename)){
		std::ifstream l_File(a_Filename.c_str(), std::ios::binary | std::ios::in);
		l_File.exceptions(std::ios::failbit | std::ios::badbit);

		return a_ImageFile->load(l_File);
	}

	return 0;
}

canvas *image_file_tga::load(std::istream &a_InputStream) const{
	unsigned char *l_Header = new unsigned char[18];
	a_InputStream.read(reinterpret_cast<char*>(l_Header), 18);

	int l_Width     = l_Header[12] + (l_Header[13] << 8);
	int l_Height    = l_Header[14] + (l_Header[15] << 8);
	int l_Depth     = l_Header[16];
	int l_Size      = l_Width * l_Height;
	int l_PixelSize = l_Depth >> 3;

	// check the orientation of the TGA file because TGAs can be stored up side down
	bool l_IsTopLeft = !(bool)((l_Header[17] & (1 << 5)) == (1 << 5));

	delete[] l_Header;

	// read complete TGA file into memory
	unsigned char *l_Data = new unsigned char[l_Size * l_PixelSize];
	a_InputStream.read(reinterpret_cast<char*>(l_Data), l_Size * l_PixelSize);

	// store pointer to the begin of the file so we can delete it later on
	unsigned char *l_Begin = l_Data;

	if(l_IsTopLeft){
		// read from the end of the file up
		l_Data += (l_Height - 1) * l_Width * l_PixelSize;
	}

	canvas *l_Canvas = read_image(l_Data, l_Width, l_Height, l_Depth, l_IsTopLeft);
	delete[] l_Begin;
	return l_Canvas;
}

canvas *image_file_tga::read_image(unsigned char *a_Data, int a_Width, int a_Height, int a_Depth, bool a_IsTopLeft) const{
	int l_PixelSize  = a_Depth >> 3;
	canvas *l_Canvas = new canvas(a_Width, a_Height);
	l_Canvas->lock();
	if(a_Depth == 32 || a_Depth == 24){
		for(int y = 0; y < a_Height; y++){
			for(int x = 0; x < a_Width; x++){
				pixel_t l_Red   = a_Data[x * l_PixelSize + 0];
				pixel_t l_Green = a_Data[x * l_PixelSize + 1];
				pixel_t l_Blue  = a_Data[x * l_PixelSize + 2];
				// skip alpha channel for 32 bit depth
				l_Canvas->paint(x, y, l_Canvas->mix_color(l_Red, l_Green, l_Blue)); 
			}
			a_Data += a_Width * l_PixelSize * (a_IsTopLeft ? -1 : 1);
		}
	}else if(a_Depth == 16){
		for(int y = 0; y < a_Height; y++){
			for(int x = 0; x < a_Width; x++){
				// ARRRRRGG GGGBBBBB
				short l_Color = *reinterpret_cast<short*>(a_Data + x);
				int l_Red   = ((l_Color & (31 << 10)) >> 10) << 3;
				int l_Green = ((l_Color & (31 <<  5)) >> 5) << 3;
				int l_Blue  =  (l_Color & 31) << 3;
				// ignores alpha bit
				l_Canvas->paint(x, y, l_Canvas->mix_color(l_Red, l_Green, l_Blue));
			}
			a_Data += a_Width * l_PixelSize * (a_IsTopLeft ? -1 : 1);
		}
	}
	l_Canvas->unlock();
	return l_Canvas;
}

void image_file_tga::store(std::ostream &a_OutputStream, canvas *a_Canvas) const{
	throw "not implemented";
}

canvas *image_file_bmp::load(std::istream &a_InputStream) const{
	// TODO: we might do some more error checking

	unsigned char l_FileHeader[14];
	unsigned char l_InfoHeader[40];

	a_InputStream.read(reinterpret_cast<char*>(l_FileHeader), sizeof(l_FileHeader));
	a_InputStream.read(reinterpret_cast<char*>(l_InfoHeader), sizeof(l_InfoHeader));

	int l_Width   = l_InfoHeader[4] + (l_InfoHeader[5] << 8) 
		+ (l_InfoHeader[6] << 16)  + (l_InfoHeader[7] << 24);

	int l_Height  = l_InfoHeader[8] + (l_InfoHeader[9] << 8) 
		+ (l_InfoHeader[10] << 16) + (l_InfoHeader[11] << 24);

	int l_PixelSize = (l_InfoHeader[14] + (l_InfoHeader[15] << 8)) >> 3;

	int l_Size = l_PixelSize * l_Width * l_Height;

	unsigned char *l_Data = new unsigned char[l_Size];
	// store the start of the file so we can modify the pointer 
	// and delete the data afterwards
	unsigned char *l_Start = l_Data;

	a_InputStream.read(reinterpret_cast<char*>(l_Data), l_Size);

	canvas *a_Canvas = new canvas(l_Width, l_Height);
	assert(a_Canvas != 0);
	a_Canvas->lock();

	l_Data += (l_Height - 1) * l_Width * l_PixelSize;

	for(int y = 0; y < l_Height; y++){
		for(int x = 0; x < l_Width; x++){
			// bitmaps are stored in BGR format
			unsigned char l_Red   = l_Data[x * l_PixelSize + 2];
			unsigned char l_Green = l_Data[x * l_PixelSize + 1];
			unsigned char l_Blue  = l_Data[x * l_PixelSize];

			a_Canvas->paint(x, y, a_Canvas->mix_color(l_Red, l_Green, l_Blue));
		}

		l_Data -= l_Width * l_PixelSize;
	}

	a_Canvas->unlock();

	delete[] l_Start;
	return a_Canvas;
}

void image_file_bmp::store(std::ostream &a_OutputStream, canvas *a_Canvas) const{
	const size_t l_PixelSize = 3;

	if(!a_OutputStream || !a_OutputStream.good())
		return;

	unsigned char l_FileHeader[14] = {'B','M', 0, 0, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0};
	unsigned char l_InfoHeader[40] = {40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 24, 0};
	unsigned char l_Pad[3]         = {0, 0, 0};

	size_t l_Size = sizeof(l_FileHeader) + sizeof(l_InfoHeader) 
		+ l_PixelSize * a_Canvas->get_width() * a_Canvas->get_height();

	l_FileHeader[2]  = static_cast<unsigned char>(l_Size);
	l_FileHeader[3]  = static_cast<unsigned char>(l_Size >> 8);
	l_FileHeader[4]  = static_cast<unsigned char>(l_Size >> 16);
	l_FileHeader[5]  = static_cast<unsigned char>(l_Size >> 24);

	l_InfoHeader[4]  = static_cast<unsigned char>(a_Canvas->get_width());
	l_InfoHeader[5]  = static_cast<unsigned char>(a_Canvas->get_width() >> 8);
	l_InfoHeader[6]  = static_cast<unsigned char>(a_Canvas->get_width() >> 16);
	l_InfoHeader[7]  = static_cast<unsigned char>(a_Canvas->get_width() >> 24);

	l_InfoHeader[8]  = static_cast<unsigned char>(a_Canvas->get_height());
	l_InfoHeader[9]  = static_cast<unsigned char>(a_Canvas->get_height() >> 8);
	l_InfoHeader[10] = static_cast<unsigned char>(a_Canvas->get_height() >> 16);
	l_InfoHeader[11] = static_cast<unsigned char>(a_Canvas->get_height() >> 24);

	a_OutputStream.write(reinterpret_cast<char*>(l_FileHeader), sizeof(l_FileHeader));
	a_OutputStream.write(reinterpret_cast<char*>(l_InfoHeader), sizeof(l_InfoHeader));

	unsigned char l_CharColor[3] = {0, 0, 0};
	a_Canvas->lock();

	// scanlines in BMP files are stored backwards for some reason.
	for(int y = a_Canvas->get_height()-1; y; y--){
		for(int x = 0; x < a_Canvas->get_width(); x++){
			math::color3 l_Color = a_Canvas->color_at(x, y);

			l_CharColor[2] = static_cast<unsigned char>(255.f * l_Color.m_R);
			l_CharColor[1] = static_cast<unsigned char>(255.f * l_Color.m_G);
			l_CharColor[0] = static_cast<unsigned char>(255.f * l_Color.m_B);

			a_OutputStream.write(reinterpret_cast<char*>(l_CharColor), sizeof(l_CharColor));
		}

		size_t l_LeftOvers = (4 - (a_Canvas->get_width() * l_PixelSize) & 3) & 3;
		for(size_t x = 0; x < l_LeftOvers; x++){
			a_OutputStream.write(reinterpret_cast<char*>(l_Pad), sizeof(l_Pad));
		}
	}

	a_Canvas->unlock();
}
